package com.junxonline.instant.admin.service.device.impl;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.lang.Assert;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.github.yitter.idgen.YitIdHelper;
import com.junxonline.instant.admin.service.device.TerminalService;
import com.junxonline.instant.common.annotation.DataScope;
import com.junxonline.instant.common.constant.RedisConstant;
import com.junxonline.instant.common.constant.SQLConstant;
import com.junxonline.instant.common.constant.TerminalInfoConstant;
import com.junxonline.instant.common.entity.common.CommonReqPage;
import com.junxonline.instant.common.entity.common.CommonRespPage;
import com.junxonline.instant.common.enumeration.AdminErrorEnum;
import com.junxonline.instant.common.exception.AdminBizException;
import com.junxonline.instant.common.util.common.RedisUtils;
import com.junxonline.instant.dao.entity.dto.terminal.CommandSettingDTO;
import com.junxonline.instant.dao.entity.dto.terminal.QueryDisplayTypeDTO;
import com.junxonline.instant.dao.entity.dto.terminal.QueryScreenshotDTO;
import com.junxonline.instant.dao.entity.dto.terminal.QueryVolumeDTO;
import com.junxonline.instant.dao.entity.model.config.Organization;
import com.junxonline.instant.dao.entity.model.config.Region;
import com.junxonline.instant.dao.entity.model.device.Group;
import com.junxonline.instant.dao.entity.model.device.Terminal;
import com.junxonline.instant.dao.entity.model.terminal.TerminalDisplayType;
import com.junxonline.instant.dao.entity.model.terminal.TerminalVolume;
import com.junxonline.instant.dao.entity.vo.common.IdsVO;
import com.junxonline.instant.dao.entity.vo.device.BatchTerminalVO;
import com.junxonline.instant.dao.entity.vo.device.TerminalVO;
import com.junxonline.instant.dao.entity.vo.iobean.ExcelOutBean;
import com.junxonline.instant.dao.entity.vo.iobean.ExcelSheets;
import com.junxonline.instant.dao.entity.vo.iobean.ExcelTerminal;
import com.junxonline.instant.dao.mapper.config.OrganizationMapper;
import com.junxonline.instant.dao.mapper.config.RegionMapper;
import com.junxonline.instant.dao.mapper.device.GroupMapper;
import com.junxonline.instant.dao.mapper.device.TerminalMapper;
import com.junxonline.instant.dao.mapper.terminal.TerminalDisplayTypeMapper;
import com.junxonline.instant.dao.mapper.terminal.TerminalVolumeMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.*;

/**
 * 终端管理接口实现类
 *
 * @author JunX
 * @date 2021-09-22
 */
@Service
@Transactional
public class TerminalServiceImpl extends ServiceImpl<TerminalMapper, Terminal> implements TerminalService {

    @Autowired
    private RedisUtils redisUtils;

    @Autowired
    private OrganizationMapper organizationMapper;

    @Autowired
    private TerminalMapper terminalMapper;

    @Autowired
    private TerminalVolumeMapper terminalVolumeMapper;

    @Autowired
    private TerminalDisplayTypeMapper terminalDisplayTypeMapper;

    @Autowired
    private GroupMapper groupMapper;

    @Autowired
    private RegionMapper regionMapper;


    /**
     * 查询终端列表
     *
     * @param vo      请求参数
     * @param wrapper 条件查询器
     * @return CommonRespPage<Terminal>
     */
    @Override
    @DataScope(isExistCreateId = false)
    public CommonRespPage<Terminal> queryList(CommonReqPage<TerminalVO> vo, QueryWrapper<Terminal> wrapper) {
        wrapper.eq(vo.getQueryParams().getOrganizationId() != null, "t.organization_id", vo.getQueryParams().getOrganizationId());
        wrapper.eq(vo.getQueryParams().getType() != null, "t.type", vo.getQueryParams().getType());
        wrapper.like(StrUtil.isNotBlank(vo.getQueryParams().getName()), "t.name", vo.getQueryParams().getName());
        wrapper.like(StrUtil.isNotBlank(vo.getQueryParams().getCode()), "t.code", vo.getQueryParams().getCode());
        wrapper.like(StrUtil.isNotBlank(vo.getQueryParams().getSoftwareVersion()), "t.software_version", vo.getQueryParams().getSoftwareVersion());
        wrapper.eq(vo.getQueryParams().getStatus() != null, "t.status", vo.getQueryParams().getStatus());
        wrapper.eq(vo.getQueryParams().getGroupId() != null, "t.group_id", vo.getQueryParams().getGroupId());
        wrapper.orderByDesc("t.heart_beat_time");
        return CommonRespPage.success(terminalMapper.queryList(new Page<>(vo.getPage(), vo.getPageSize()), wrapper));
    }

    /**
     * 查询终端音量设置
     *
     * @param id ID
     * @return QueryVolumeDTO
     */
    @Override
    public QueryVolumeDTO queryVolumeSetting(Long id) {
        Terminal terminal = terminalMapper.selectById(id);
        if (terminal == null) {
            return null;
        }
        String terminalCode = terminal.getCode();
        // 从数据库中查询
        TerminalVolume model = terminalVolumeMapper.selectOne(new LambdaQueryWrapper<TerminalVolume>().eq(TerminalVolume::getTerminalCode, terminalCode).last(SQLConstant.LIMIT_1));
        if (model != null) {
            return new QueryVolumeDTO(model.getVolume());
        }
        return null;
    }

    /**
     * 查询终端音量
     *
     * @param id ID
     * @return QueryCommandDTO
     */
    @Override
    public QueryVolumeDTO queryVolume(Long id) {
        Terminal terminal = terminalMapper.selectById(id);
        if (terminal == null) {
            return null;
        }
        String terminalCode = terminal.getCode();
        // 查询缓存中是否有数据
        Integer volume = (Integer) redisUtils.hget(RedisConstant.KEY_TERMINAL_VOLUME + terminalCode, TerminalInfoConstant.KEY_VOLUME);
        if (volume != null) {
            return new QueryVolumeDTO(volume);
        }
        return null;
    }

    /**
     * 查询终端显示方式设置
     *
     * @param id ID
     * @return QueryDisplayTypeDTO
     */
    @Override
    public QueryDisplayTypeDTO queryDisplayTypeSetting(Long id) {
        Terminal terminal = terminalMapper.selectById(id);
        if (terminal == null) {
            return null;
        }
        String terminalCode = terminal.getCode();
        // 从数据库中查询
        TerminalDisplayType model = terminalDisplayTypeMapper.selectOne(new LambdaQueryWrapper<TerminalDisplayType>().eq(TerminalDisplayType::getTerminalCode, terminalCode).last(SQLConstant.LIMIT_1));
        if (model != null) {
            return new QueryDisplayTypeDTO(model.getDisplayType());
        }
        return null;
    }

    /**
     * 查询终端显示方式
     *
     * @param id ID
     * @return QueryDisplayTypeDTO
     */
    @Override
    public QueryDisplayTypeDTO queryDisplayType(Long id) {
        Terminal terminal = terminalMapper.selectById(id);
        if (terminal == null) {
            return null;
        }
        String terminalCode = terminal.getCode();
        // 查询缓存中是否有数据
        Integer displayType = (Integer) redisUtils.hget(RedisConstant.KEY_TERMINAL_DISPLAY_TYPE + terminalCode, TerminalInfoConstant.KEY_DISPLAY_TYPE);
        if (displayType != null) {
            return new QueryDisplayTypeDTO(displayType);
        }
        return null;
    }

    /**
     * 查询终端截屏
     *
     * @param id ID
     * @return QueryScreenshotDTO
     */
    @Override
    public QueryScreenshotDTO queryScreenshot(Long id) {
        Terminal terminal = terminalMapper.selectById(id);
        if (terminal == null) {
            return null;
        }
        String terminalCode = terminal.getCode();
        // 查询缓存中是否有数据
        String url = (String) redisUtils.hget(RedisConstant.KEY_TERMINAL_SCREENSHOT + terminalCode, TerminalInfoConstant.KEY_SCREENSHOT);
        if (StrUtil.isNotBlank(url)) {
            // 清除缓存中的数据
            redisUtils.hdel(RedisConstant.KEY_TERMINAL_SCREENSHOT + terminalCode, TerminalInfoConstant.KEY_SCREENSHOT);
            return new QueryScreenshotDTO(url);
        }
        return null;
    }

    /**
     * 查询终端指令设置
     *
     * @param id ID
     * @return CommandSettingDTO
     */
    @Override
    public CommandSettingDTO queryCommandSetting(Long id) {
        // 查询终端
        Terminal terminal = this.query(id);
        CommandSettingDTO dto = new CommandSettingDTO();
        String terminalCode = terminal.getCode();
        // 查询音量设置
        TerminalVolume volume = terminalVolumeMapper.selectOne(new LambdaQueryWrapper<TerminalVolume>().eq(TerminalVolume::getTerminalCode, terminalCode).last(SQLConstant.LIMIT_1));
        if (volume != null) {
            dto.setVolume(volume.getVolume());
        }
        // 查询显示方式
        TerminalDisplayType displayType = terminalDisplayTypeMapper.selectOne(new LambdaQueryWrapper<TerminalDisplayType>().eq(TerminalDisplayType::getTerminalCode, terminalCode).last(SQLConstant.LIMIT_1));
        if (displayType != null) {
            dto.setDisplayType(displayType.getDisplayType());
        }
        return dto;
    }

    /**
     * 查询终端
     *
     * @param id ID
     * @return Terminal
     */
    @Override
    public Terminal query(Long id) {
        Terminal terminal = terminalMapper.selectById(id);
        Assert.notNull(terminal, AdminErrorEnum.DATA_IS_NOT_EXISTS.getMsg());
        return terminal;
    }

    /**
     * 新增终端
     *
     * @param terminal 请求参数
     */
    @Override
    public void add(Terminal terminal) {
        // 获取雪花ID
        terminal.setId(YitIdHelper.nextId());
        terminal.setStatus(1);
        terminal.setCreateTime(DateUtil.date());
        terminalMapper.insert(terminal);
    }

    /**
     * 编辑终端
     *
     * @param terminal 请求参数
     */
    @Override
    public void edit(Terminal terminal) {
        Integer count = terminalMapper.selectCount(new LambdaQueryWrapper<Terminal>()
                .ne(Terminal::getId, terminal.getId())
                .eq(Terminal::getOrganizationId, terminal.getOrganizationId())
                .eq(Terminal::getCode, terminal.getCode()));
        if (count > 0) {
            throw new AdminBizException(AdminErrorEnum.DATA_IS_EXISTS);
        }
        // 如果变更了组织机构 则清空分组和线路
        Terminal model = terminalMapper.selectByCode(terminal.getCode());
        LambdaUpdateWrapper<Terminal> wrapper = new LambdaUpdateWrapper<>();
        if (!model.getOrganizationId().equals(terminal.getOrganizationId())) {
            wrapper.set(Terminal::getGroupId, null);
        }
        terminalMapper.update(terminal, wrapper);
    }

    /**
     * 批量编辑终端
     *
     * @param vo 请求参数
     */
    @Override
    public void batchEdit(BatchTerminalVO vo) {
        LambdaUpdateWrapper<Terminal> wrapper = new LambdaUpdateWrapper<>();
        for (Long id : vo.getIds()) {
            Terminal terminal = new Terminal();
            terminal.setId(id);
            terminal.setOrganizationId(vo.getOrganizationId());
            if (vo.getOrganizationId() != null) {
                // 如果变更了组织机构 则清空分组和线路
                Terminal model = terminalMapper.selectById(id);
                if (!model.getOrganizationId().equals(terminal.getOrganizationId())) {
                    wrapper.set(Terminal::getGroupId, null);
                }
                terminal.setOrganizationId(vo.getOrganizationId());
            }
            if (vo.getGroupId() != null) {
                terminal.setGroupId(vo.getGroupId());
            }
            if (vo.getCityId() != null) {
                terminal.setCityId(vo.getCityId());
            }
            if (vo.getBusLineId() != null) {
            }
            terminalMapper.update(terminal, wrapper);
        }
    }

    /**
     * 删除终端
     *
     * @param ids 请求参数
     */
    @Override
    public void delete(List<Long> ids) {
        // TODO 删除所有关联的数据
        terminalMapper.deleteBatchIds(ids);
    }

    /**
     * 导出
     *
     * @param vo 请求参数
     * @return ExcelOutBean
     */
    @Override
    public ExcelOutBean exportExcel(IdsVO<List<Long>> vo) {
        ExcelOutBean outBean = new ExcelOutBean();
        outBean.setName("终端信息_" + DateUtil.format(new Date(), "yyyy-MM-dd_HH-mm") + ".xlsx");
        outBean.setNum(1);

        List<Terminal> terminalList;
        QueryWrapper<Terminal> wrapper = new QueryWrapper<>();
        if (vo == null || vo.getIds() == null || vo.getIds().size() <= 0) {
            //导出全部
            wrapper.ge("t.id", 0);
        } else {
            //导出部分
            wrapper.in("t.id", vo.getIds());
        }
        terminalList = terminalMapper.queryList(wrapper);

        List<ExcelSheets> excelSheetsList = new ArrayList<>();
        ExcelSheets sheet1 = new ExcelSheets();
        sheet1.setName(ExcelTerminal.SHEET_NAME);
        sheet1.setType(ExcelSheets.TYPE_JSON);
        sheet1.setWidth(ExcelTerminal.getWidths());
        sheet1.setHeader(ExcelTerminal.getHeaders());

        List<ExcelTerminal> excelTerminalList = new ArrayList<>();
        ExcelTerminal excelTerminal;
        int order = 1;
        for (Terminal item : terminalList) {
            excelTerminal = new ExcelTerminal();
            excelTerminal.setId(order);
            excelTerminal.setName(item.getName());
            excelTerminal.setCode(item.getCode());
            excelTerminal.setVersion(item.getSoftwareVersion());
            excelTerminal.setIp(item.getInternetIp());
            excelTerminal.parseMemory(item.getDiskSpace());
            excelTerminal.setHeartbeat(item.getHeartBeatTime());
            excelTerminal.parseState(item.getStatus());
            excelTerminal.setOrganization(item.getOrganizationName());
            excelTerminal.setCity(item.getCityName() == null ? "" : item.getCityName());
            excelTerminal.setGroup(item.getGroupName() == null ? "" : item.getGroupName());
            excelTerminal.setCity(item.getCityName());
            excelTerminal.setGroup(item.getGroupName());
            excelTerminalList.add(excelTerminal);
            order++;
        }

        sheet1.setData(excelTerminalList);
        excelSheetsList.add(sheet1);
        outBean.setSheets(excelSheetsList);

//        System.out.println(JSONObject.toJSON(outBean).toString());
        return outBean;
    }

    /**
     * 导入
     *
     * @param jsonArray 请求参数
     * @return String
     */
    @Override
    public String importExcel(JSONArray jsonArray) {
        StringBuilder out = new StringBuilder("导入进程>>>\n");

        List<ExcelTerminal> excelTerminalList = new ArrayList<>();
        for (Object sheet : jsonArray) {//sheets 遍历
            JSONObject sheetObj = (JSONObject) sheet;

            //data
            JSONArray arrayData = sheetObj.getJSONArray("data");
            for (Object item : arrayData) {
                JSONObject dataObj = (JSONObject) item;
                ExcelTerminal excelTerminal = new ExcelTerminal();
                for (Map.Entry<String, Object> entry : dataObj.entrySet()) {
                    excelTerminal.setValue(entry.getKey(), entry.getValue());
//                    String key = entry.getKey();
//                    Object value = entry.getValue();
//                    System.out.println("key:" + key + ", value: " + value);
                }
                excelTerminalList.add(excelTerminal);
            }
        }

        Assert.isFalse(excelTerminalList.isEmpty(), "数据为空！");

        out.append("总条数：").append(excelTerminalList.size()).append("\n");
        for (ExcelTerminal item : excelTerminalList) {
            //1. 检验终端是否存在
            Terminal terminal = terminalMapper.selectOne(new LambdaQueryWrapper<Terminal>()
                    .eq(Terminal::getCode, item.getCode())
                    .last(SQLConstant.LIMIT_1));
            if (terminal != null) {
                terminal.setName(item.getName());
//                terminal.setCode(info.getCode());

                //通过名称获取对应id,然后绑定
                //所在地: sys_region
                if (!StrUtil.isEmpty(item.getCity())) {
                    Region region = regionMapper.selectOne(new LambdaQueryWrapper<Region>()
                            .eq(Region::getType, 2)
                            .eq(Region::getName, item.getCity())
                            .last(SQLConstant.LIMIT_1));
                    if (region != null) {
                        terminal.setCityId(region.getId());
                    } else {
                        out.append("所在地: ").append(item.getCity()).append("未找到！").append("\n");
                    }
                }

                //分组： th_group
                if (!StrUtil.isEmpty(item.getGroup())) {
                    Group group = groupMapper.selectOne(new LambdaQueryWrapper<Group>()
                            .eq(Group::getName, item.getGroup())
                            .last(SQLConstant.LIMIT_1));
                    if (group != null) {
                        terminal.setGroupId(group.getId());
                    } else {
                        out.append("分组: ").append(item.getGroup()).append("未找到！").append("\n");
                    }
                }
                int result = terminalMapper.updateById(terminal);
                out.append("导入终端：").append(item.getCode()).append(", ").append(result).append("\n");
            } else {
                out.append("终端：").append(item.getCode()).append(", 不存在！").append("\n");
            }
        }

        return out.toString();
    }

    /**
     * 查询组织机构及下级所有子机构
     *
     * @param organizationId 组织机构ID
     * @return Set<Long>
     */
    private Set<Long> getOrganizationAndChildren(Long organizationId) {
        Set<Long> organizationIds = new HashSet<>();
        organizationIds.add(organizationId);
        List<Organization> childrenOrganizations = organizationMapper.queryChildren(organizationId);
        // 解析树形数据为平级结构
        treeToFlat(organizationIds, childrenOrganizations);
        return organizationIds;
    }

    /**
     * 树形结构转为扁平结构
     *
     * @param organizationIds 扁平集合
     * @param organizations   树形集合
     */
    private void treeToFlat(Set<Long> organizationIds, List<Organization> organizations) {
        for (Organization organization : organizations) {
            organizationIds.add(organization.getId());
            if (organization.getChildren() != null && organization.getChildren().size() > 0) {
                this.treeToFlat(organizationIds, organization.getChildren());
            }
        }
    }

}
